package vn.ugame.module.auth;

import com.google.gson.Gson;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.expressme.openid.Association;
import org.expressme.openid.Endpoint;
import org.expressme.openid.OpenIdException;
import org.expressme.openid.OpenIdManager;
import vn.ugame.exception.EntityNotFound;
import vn.ugame.exception.ExceptionCode;
import vn.ugame.exception.UniqueValueException;
import vn.ugame.model.bl.Authentication;
import vn.ugame.model.bl.ModuleManager;
import vn.ugame.model.entity.Language;
import vn.ugame.module.*;
import vn.ugame.utility.EmailSender;
import vn.ugame.utility.RandomString;
import vn.ugame.utility.StringEscape;

public class AuthModule extends AbstractModule implements Module {

    static final long ONE_HOUR = 3600000L;
    static final long TWO_HOUR = ONE_HOUR * 2L;
    static final String ATTR_MAC = "openid_mac";
    static final String ATTR_ALIAS = "openid_alias";

    public AuthModule() {
    }

    @ActionConfig(urlPattern = {""}, modules = {"register"})
    public void register() throws ServletException, IOException {
        ModuleManager manager = new ModuleManager();
        /**
         * check security
         */
        if (getRequest().getSession(true).getAttribute("AuthPrincipal") != null) {
            try {
                vn.ugame.model.entity.Module storeModule = manager.findLocalModules(ModuleType.STORE_ALL, getConfig().getLocale()).get(0);
                getResponse().sendRedirect("/" + storeModule.getName());
                return;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        /**
         * declare service
         */
        Map<String, Integer> exceptions = new HashMap<String, Integer>();

        if (getRequest().getMethod().equalsIgnoreCase("POST")) {
            /**
             * Get parameters from request
             */
            String email = getRequest().getParameter("email");
            String password = getRequest().getParameter("password");
            String confirmPassword = getRequest().getParameter("confirmPassword");
            String fullName = getRequest().getParameter("fullName");
            String phoneNumber = getRequest().getParameter("phoneNumber");
            String accept = getRequest().getParameter("accept");
            boolean newsletter = false;

            /**
             * Validate parameters
             */
            if (email == null || email.trim().isEmpty()) {
                exceptions.put("email", ExceptionCode.REQUIRED_FIELD);
            }

            if (password == null || password.trim().isEmpty()) {
                exceptions.put("password", ExceptionCode.REQUIRED_FIELD);
            }

            if (fullName == null || fullName.trim().isEmpty()) {
                exceptions.put("fullName", ExceptionCode.REQUIRED_FIELD);
            }

            if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
                exceptions.put("phoneNumber", ExceptionCode.REQUIRED_FIELD);
            }

            String pattern = "\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
            if (email != null && !email.matches(pattern) && !exceptions.containsKey("email")) {
                exceptions.put("email", ExceptionCode.INVALID_REQUEST_PARAMETER);
            }

            pattern = "\\w{6,24}";
            if (password != null && !password.matches(pattern) && !exceptions.containsKey("password")) {
                exceptions.put("password", ExceptionCode.INVALID_PASSWORD);
            }

            if (confirmPassword != null && !confirmPassword.equals(password) && !exceptions.containsKey("password")) {
                exceptions.put("confirmPassword", ExceptionCode.PASSWORD_NOT_MATCH);
            }

            pattern = "\\d{7,20}";
            if (phoneNumber != null && !phoneNumber.matches(pattern) && !exceptions.containsKey("phoneNumber")) {
                exceptions.put("phoneNumber", ExceptionCode.INVALID_REQUEST_PARAMETER);
            }

            if ((accept == null || accept.trim().isEmpty()) && exceptions.isEmpty()) {
                exceptions.put("accept", ExceptionCode.INVALID_REQUEST_PARAMETER);
            }

            if (getRequest().getParameter("newsletter") != null) {
                newsletter = true;
            }
            /**
             * register
             */
            if (exceptions.isEmpty()) {
                Authentication auth = new Authentication();
                try {
                    auth.register(email, password, fullName, phoneNumber, newsletter);
                    //Put send email code here
                    EmailSender.send(new String[]{email}, "Welcome to UGame", "template/email/DefaultTemplate.html", "Welcome to UGame", "Welcome to UGame");

                    HttpSession session = getRequest().getSession();
                    session.setAttribute("AuthPrincipal", email);

                    String locale = getConfig().getLocale().getLanguage();
                    locale = locale.substring(0, 2) + "_" + locale.substring(3).toUpperCase();
                    ResourceBundle rb = ResourceBundle.getBundle("vn.ugame.bundle.Language_" + locale);
                    String msg = rb.getString("ugame.message-1");
                    session.setAttribute("message", msg);

                    vn.ugame.model.entity.Module storeModule = manager.findLocalModules(ModuleType.STORE_ALL, getConfig().getLocale()).get(0);
                    getResponse().sendRedirect("/" + storeModule.getName());
                    return;
                } catch (UniqueValueException ex) {
                    if (ex.getCode() == ExceptionCode.UNIQUE_EMAIL && !exceptions.containsKey("email")) {
                        exceptions.put("email", ex.getCode());
                    } else if (ex.getCode() == ExceptionCode.UNIQUE_PHONE_NUMBER && !exceptions.containsKey("phoneNumber")) {
                        exceptions.put("phoneNumber", ex.getCode());
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        getRequest().setAttribute("exceptions", exceptions);

        try {

            /**
             * Prepare default model for store module
             */
            // Create default model
            List<vn.ugame.model.entity.Module> roots = manager.findRootModules(getConfig().getLocale());
            List<Language> languages = new ArrayList<Language>();
            List<vn.ugame.model.entity.Module> localModules = manager.findLocalModules(getConfig().getId());
            for (vn.ugame.model.entity.Module module : localModules) {
                vn.ugame.model.entity.Language language = new Language();
                if (module.getName().equals(getConfig().getName())) {
                    language.setCurrent(true);
                } else {
                    language.setCurrent(false);
                }
                language.setLocale(module.getLocale());
                language.setUrl("/" + module.getName());
                languages.add(language);
            }
            List<vn.ugame.model.entity.Module> authModules = manager.findLocalModules(ModuleType.AUTH, getConfig().getLocale());
            vn.ugame.model.entity.Module loginModule = null;
            vn.ugame.model.entity.Module registerModule = null;
            for (vn.ugame.model.entity.Module module : authModules) {
                if (module.getDefaultName().equalsIgnoreCase("login")) {
                    loginModule = module;
                } else {
                    registerModule = module;
                }
            }

            //Save default model into request scope
            getRequest().setAttribute("config", getConfig());
            getRequest().setAttribute("roots", roots);
            getRequest().setAttribute("languages", languages);
            getRequest().setAttribute("loginModule", loginModule);
            getRequest().setAttribute("registerModule", registerModule);

            RequestDispatcher dispatcher = getRequest().getRequestDispatcher("/register.jsp");
            dispatcher.forward(getRequest(), getResponse());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @ActionConfig(urlPattern = {""}, modules = {"login"})
    public void login() throws ServletException, IOException {
        /**
         * Get parameters from request
         */
        String email = getRequest().getParameter("email");
        String password = getRequest().getParameter("password");

        /*
         * Replace special charater
         */
        email = StringEscape.getInstance().escapeHtml(email.trim());
        password = StringEscape.getInstance().escapeHtml(password.trim());

        /**
         * login
         */
        Authentication auth = new Authentication();
        try {
            auth.login(email, password);
            HttpSession session = getRequest().getSession();
            session.setAttribute("AuthPrincipal", email);
        } catch (EntityNotFound ex) {
            String locale = getConfig().getLocale().getLanguage();
            locale = locale.substring(0, 2) + "_" + locale.substring(3).toUpperCase();
            ResourceBundle rb = ResourceBundle.getBundle("vn.ugame.bundle.Language_" + locale);
            String msg = rb.getString("ugame.exception-" + ex.getCode());

            String json = new Gson().toJson(msg);

            getResponse().setContentType("application/json");
            getResponse().setCharacterEncoding("UTF-8");
            getResponse().getWriter().write(json);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @ActionConfig(urlPattern = {"openid", "openid/(?<op>[a-zA-Z0-9_-]+)$"}, modules = {"login"})
    public void loginWithOpenId(@Param("op") String op) throws ServletException, IOException {
        try {
            OpenIdManager manager = new OpenIdManager();

            String requestURL = getRequest().getRequestURL().toString();
            requestURL = requestURL.endsWith("/") ? requestURL.substring(0, requestURL.lastIndexOf("/")) : requestURL;
            String http = requestURL.substring(0, requestURL.indexOf("://") + 3);
            requestURL = requestURL.substring(requestURL.indexOf("://") + 3, requestURL.length());
            String domain = requestURL.substring(0, requestURL.indexOf("/"));

            manager.setRealm(http + domain);
            manager.setReturnTo(http + domain + "/login/openid");

            if (op == null) {
                // check nonce
                checkNonce(getRequest().getParameter("openid.response_nonce"));
                // get authentication
                HttpSession session = getRequest().getSession();
                byte[] mac_key = (byte[]) session.getAttribute(ATTR_MAC);
                String alias = (String) session.getAttribute(ATTR_ALIAS);
                org.expressme.openid.Authentication authentication = manager.getAuthentication(getRequest(), mac_key, alias);

                String email = authentication.getEmail();
                session.setAttribute("AuthPrincipal", email);

                Authentication auth = new Authentication();

                try {
                    String password = RandomString.getInstance().getRandomString(6);
                    auth.register(email, password, authentication.getFullname(), null, true);
                    EmailSender.send(new String[]{email}, "Welcome to UGame", "template/email/DefaultTemplate.html", "Welcome to UGame<br/>Your password is: " + password, "Welcome to UGame<br/>Your password is: " + password);
                } catch (UniqueValueException ex) {
                    if (ex.getCode() != ExceptionCode.UNIQUE_EMAIL) {
                        ex.printStackTrace();
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

                String returnURL = (String) session.getAttribute("returnURL");
                if (returnURL != null) {
                    session.removeAttribute("returnURL");
                    getResponse().sendRedirect(returnURL);
                } else {
                    getResponse().sendRedirect("/store");
                }
            } else if ("Google".equals(op) || "Yahoo".equals(op)) {
                String returnURL = getRequest().getParameter("returnURL");

                Endpoint endpoint = manager.lookupEndpoint(op);
                Association association = manager.lookupAssociation(endpoint);
                HttpSession session = getRequest().getSession();
                session.setAttribute(ATTR_MAC, association.getRawMacKey());
                session.setAttribute(ATTR_ALIAS, endpoint.getAlias());
                session.setAttribute("returnURL", returnURL);
                String url = manager.getAuthenticationUrl(endpoint, association);
                getResponse().sendRedirect(url);
            } else {
                getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @ActionConfig(urlPattern = {""}, modules = {"logout"})
    public void logout() throws ServletException, IOException {
        try {
            /**
             * logout
             */
            HttpSession session = getRequest().getSession();
            session.removeAttribute("AuthPrincipal");
            
            getResponse().sendRedirect("/");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void checkNonce(String nonce) {
        if (nonce == null || nonce.length() < 20) {
            throw new OpenIdException("Verify failed.");
        }
        long nonceTime = getNonceTime(nonce);
        long diff = System.currentTimeMillis() - nonceTime;
        if (diff < 0) {
            diff = (-diff);
        }
        if (diff > ONE_HOUR) {
            throw new OpenIdException("Bad nonce time.");
        }
    }

    private long getNonceTime(String nonce) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(nonce.substring(0, 19) + "+0000").getTime();
        } catch (ParseException e) {
            throw new OpenIdException("Bad nonce time.");
        }
    }
}
